<?php
/**
 * Created by PhpStorm.
 * Author: tqq
 * Email tianqiq@gmail.com
 * Date: 2015-12-11
 * Time: 20:57
 */

/**
 * 配置信息
 */
$config = array(

    'db' => array(
        'def' => array(
            'dsn' => 'mysql:host=localhost;dbname=blog',
            'user' => 'root',
            'pwd' => '123123'
        ),
        'type' => array(
            'dsn' => 'mysql:host=localhost;dbname=blog1',
            'user' => 'root',
            'pwd' => '123123',
            'options' => array(),
            'charset' => 'utf8'
        )
    ),

    'ini_files' => array(),

    'model_path' => 'app/models',
    'view_path' => 'app/views',
    'controller_path' => 'app/controllers',
    'tool_path' => 'app/tools',
    'default_controller' => 'home',

    '404' => '/404.html'
);


defined('ROOT') ? Fox::getInstance()->run() : die('error! ROOT undefinition');

/*** MVC核心 beg***/

/**
 * 显示一个视图文件 $view 可以是路径，不需要.php后缀
 * @param $view
 */
function view($view) {
    global $config;
    Fox::getInstance()->end();
    include(ROOT . '/' . $config['view_path'] . '/' . strtolower($view) . '.php');
}

/**
 * @desc 输出js脚本，http头：application/javascript
 * @param $js
 */
function js($js) {
    response($js, "application/javascript");
}

/**
 * 什么都不输出
 */
function none() {
    html('');
}

/**
 * 通过输出js脚本弹出提示，并跳转到指定url
 * @param $message
 * @param string $url
 */
function alert($message, $url = '#') {
    html("<script>alert('$message');location.href='$url';</script>");
}

/**
 * 输出一段纯文本 http头：text/plain
 * @param $text
 */
function text($text) {
    response($text, "text/plain");
}

/**
 * 输出json http头：application/json
 * @param $data
 */
function json($data) {
    response(json_encode($data), "application/json");
}

/**
 * 输出html http头：text/html
 * @param $html
 */
function html($html) {
    response($html, "text/html");
}

/**
 * 重定向到指定url
 * @param $url
 * @param int $code 状态码，默认301
 */
function redirect($url, $code = 301) {
    if ($code == 301) {
        header("HTTP/1.1 301 Moved Permanently");
    } else {
        header("HTTP/1.1 302 Found");
    }
    header('Cache-Control:no-cache');
    header("location:$url");
    die();
}

/**
 * 输出指定数据和http头
 * @param $data
 * @param $content_type
 */
function response($data, $content_type) {
    Fox::getInstance()->end();
    header("Content-Type: $content_type");
    echo $data;
}

/**
 * 设置一个数据到视图数据，通过G()函数获取
 * @param $key
 * @param $val
 */
function S($key, $val) {
    Fox::getInstance()->set($key, $val);
}

/**
 * 从视图数据中， 获取数据
 * @param $key
 * @return mixed
 */
function G($key) {
    return Fox::getInstance()->get($key);
}

/**
 * 获取一个模型
 * @param $model
 * @param null $construct_args
 * @return mixed|object
 */
function M($model, $construct_args = null) {
    global $config;
    return L(ROOT . "/$config[model_path]/$model", $construct_args);
}

/**
 * 获取一个工具
 * @param $tool
 * @return mixed|object
 */
function T($tool, $construct_args = null) {
    global $config;
    return L(ROOT . "/$config[tool_path]/$tool", $construct_args);
}

/**
 * 加载指定路径的文件，只加载一次。 不需要.php或者.class.php后缀，如果是.class.php会实例化对象返回，如果include文件是returen。会返回该值。
 * @param $path 不需要.php或者.class.php后缀
 * @param null $construct_args
 * @return mixed|object
 */
function L($path, $construct_args = null) {
    static $loads = array();
    $class_name = basename($path);
    if (!isset($loads[$path])) {
        if (is_file($path . '.php')) {
            return $loads[$path] = include($path . '.php');
        } else if (is_file($path . '.class.php')) {
            include($path . '.class.php');
            $loads[$path] = '__class__';
            return is_null($construct_args) ? new $class_name() : (new ReflectionClass($class_name))->newInstanceArgs($construct_args);
        }
        die(" $path load error!");
    } else {
        return $loads[$path] === '__class__' ? (is_null($construct_args) ? new $class_name() : (new ReflectionClass($class_name))->newInstanceArgs($construct_args)) : $loads[$path];
    }
}


/**
 * Class Fox MVC核心
 */
class Fox {

    /**
     * @var 路由
     */
    private $route;

    private static $instance;

    public static function getInstance() {
        if (!self::$instance) {
            self::$instance = new Fox();
        }
        return self::$instance;
    }

    private $view_data = array();
    private $is_response = false;


    private function __construct() {
        $this->route = new Route();
    }

    public function set($key, $val) {
        $this->view_data[$key] = $val;
    }

    public function get($key) {
        return $this->view_data[$key];
    }

    public function end() {
        $this->is_response = true;
    }

    function run() {
        $this->ini();
        $this->route();
    }

    private function ini() {
        $this->view_data = array();
        global $config;
        foreach ($config['ini_files'] as $file) {
            L(ROOT . '/' . $file);
        }
    }


    private function inc($dir, $controller) {
        global $config;
        $controller = ucfirst($controller);
        $path = ROOT . "/$config[controller_path]/$dir/${controller}Controller.php";
        if ($ret = is_file($path)) {
            @include($path);
        }
        return $ret;
    }

    private function real_action($controller, $req_action, $err_controller) {
        $http_method = strtolower($_SERVER['REQUEST_METHOD']);
        if ($err_controller) {
            $real_action = '_ec';
        } else if (empty($req_action)) {
            $real_action = $http_method == 'post' && method_exists($controller, '_post') ? '_post' : '_def';
        } else if (!method_exists($controller, $req_action)) {
            $real_action = method_exists($controller, '_' . $http_method) ? '_' . $http_method : '_ea';
        } else {
            $real_action = $req_action;
        }
        return $real_action;
    }

    private $methods = array('_def', '_ea', '_post', '_put', '_get', '_delete', '_patch', '_head');

    private function real_paths($real_action) {
        $req_paths = $this->route->get_paths();
        if ($real_action == '_ec') {
            $real_paths = $req_paths;
        } else if (in_array($real_action, $this->methods)) {
            $real_paths = array_slice($req_paths, 1);
        } else {
            $real_paths = array_slice($req_paths, 2);
        }
        return $real_paths;
    }

    private function route() {

        global $config;

        $real_controller_name = $req_controller_name = $this->route->get_controller() ?: $config['default_controller']; //获取请求的controller名字
        $req_action = $this->route->get_action(); //请求的action名字;
        $dir = $this->route->get_dir();

        if ($err_controller = !$this->inc($dir, $req_controller_name)) { //尝试引入Controller文件，如果没成功引入，再引入默认controller。
            $this->inc($dir, $config['default_controller']);
            $real_controller_name = $config['default_controller'];
        }

        $controller_class = $real_controller_name . 'Controller';

        if (substr($req_action, 0, 1) != '_' && class_exists($controller_class, false)) {
            $controller = new $controller_class();
            $real_action = $this->real_action($controller, $req_action, $err_controller);
            if (method_exists($controller, $real_action)) {
                $this->exe_action($controller, $real_controller_name, $real_action, $this->real_paths($real_action));
                return;
            }
        }

        $this->e404();
    }

    private function e404() {
        global $config;
        if ($config['404']) {
            redirect($config['404'] . "?ref=$_SERVER[REQUEST_URI]", 302);
        }
        header('HTTP/1.1 404 Not Found');
        header("status: 404 Not Found");
        echo 'HTTP 404 Not Found';
        die();
    }

    private function exe_action($controller, $controller_name, $action_name, $paths) {
        if (method_exists($controller, '_before')) {
            $controller->_before($action_name, $paths);
        }
        $controller->$action_name(empty($paths) ? false : $paths[0], array_slice($paths, 1));
        $dir = $this->route->get_dir();
        if (!$this->is_response) {
            view("$dir/${controller_name}_${action_name}");
        }
        if (method_exists($controller, '_after')) {
            $controller->_after($action_name, $paths);
        }
    }

}

/**
 * Class Route 路由
 */
class Route {

    private $url;

    function __construct() {
        $this->url = $this->get_url_path();
        $this->parse();
    }

    /**
     * 无论有没有开启伪静态，获取纯mvc路径url
     * @return string
     */
    function get_url_path() {
        $name = $_SERVER['SCRIPT_NAME'];
        $url = $_SERVER['REQUEST_URI'];
        if (strpos($url, $name) === 0) {
            $url = substr($url, strlen($name));
        }
        if (($pos = stripos($url, '?')) !== false) {
            $url = substr($url, 0, $pos);
        }
        return trim($url, '/');
    }

    private $paths;

    private $dir;

    /**
     * 分析url
     */
    private function parse() {
        global $config;
        $this->paths = explode('/', $this->url);
        $this->dir = is_dir(ROOT . "/$config[controller_path]/" . $this->paths[0]) ? $this->paths[0] : '';//只支持一级子目录，绝大部分情况下一级子目录足够。这里也是为了性能考虑，不必要做递归判断
        $this->paths = empty($this->dir) ? $this->paths : array_slice($this->paths, 1);
    }

    /**
     * 获取controller路径，即controllers目录下子目录，如果没子目录则返回空，
     * @return mixed
     */
    function get_dir() {
        return $this->dir;
    }

    /**
     * 获取controller名字 没有返回空
     * @return string
     */
    function get_controller() {
        return empty($this->paths) ? '' : $this->paths[0];
    }

    /**
     * 获取action名字，没有返回空
     * @return null
     */
    function get_action() {
        return count($this->paths) > 1 ? $this->paths[1] : '';
    }

    /**
     * 获取分析后的url路径数组
     * @return mixed
     */
    function get_paths() {
        return $this->paths;
    }
}

/*** MVC核心 end***/


/*** 正则验证类 beg***/

/**
 * Class v 使用方便，扩展方便的验证类
 */
class  V {

    private static $types = array(
        'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
        'phone' => '/^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/',
        'mobile' => '/^((\(\d{2,3}\))|(\d{3}\-))?1\d{9}$/',
        'url' => '/^https?:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\'=>+!]*([^<>\"\"])*$/',
        'id' => '/^\d{15}(\d{2}[A-Za-z0-9])?$/',
        'currency' => '/^\d+(\.\d+)?$/',
        'number' => '/^\d+$/',
        'qq' => '/^[1-9]\d{4,9}$/',
        'int' => '/^[-\+]?\d+$/',
        'double' => '/^[-\+]?\d+(\.\d+)?$/',
        'en' => '/^[A-Za-z]+$/',
        'ch' => '/^[\u0391-\uFFE5]+$/',
        'name' => '/^[a-z]\w{5,}$/i',
        'ip' => '/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/'
    );

    /**
     * 数据验证函数
     * @param $type 如果type参数以.（点）开头，该参数可以为空
     * @param $val
     * @return int
     */
    public static function Is($type, $val) {
        $dot = substr($type, 0, 1) == '.';
        if ($dot) {
            if (empty($val)) {
                return true;
            }
            $type = substr($type, 1);
        }
        $test = self::$types[$type];
        return is_string($test) ? preg_match($test, $val) : $test($val);
    }

    /**
     * 扩展验证规则
     * @param $type 扩展类型名字
     * @param $reg_or_fun
     * @internal param 可以为一个正则表达式 $fun ，或者函数
     */
    public static function Extend($type, $reg_or_fun) {
        self::$types[$type] = $reg_or_fun;
    }

}

/*** 正则验证类 end***/

/*** 简单数据库操作类 begin***/

/**
 * 获取DB的快捷函数
 * @param bool|false $tableName 当前操作的tableName
 * @param $db |def 当前操作的db
 * @return DataBase
 */
function D($tableName = false, $db = 'def') {
    global $config;
    static $dbs = array();
    if (!isset($dbs[$db])) {
        $dbs[$db] = array();
    }
    if (!isset($dbs[$db][$tableName])) {
        $dbs[$db][$tableName] = new DataBase(isset($config['db']['dsn']) ? $config['db'] : $config['db'][$db]);
    }

    return $dbs[$db][$tableName]->table($tableName);
}

/**
 * Class DataBase 简单方便的数据操作类
 */
class DataBase {

    private $pdo;

    private $table = '_fox_';

    function __construct($cf) {

        $this->pdo = self::_poll($cf);// new PDO($cf['dsn'], $cf['user'], $cf['pwd'], $cf['options']);
    }

    private static function _poll($cf) {
        static $exist_pdo = array();
        if (!isset($exist_pdo[$cf['dsn']])) {
            $pdo = isset($cf['options']) ? (new PDO($cf['dsn'], $cf['user'], $cf['pwd'], $cf['options'])) :
                (new PDO($cf['dsn'], $cf['user'], $cf['pwd']));
            $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            $pdo->exec("set names " . (isset($cf['charset']) ? $cf['charset'] : 'utf8'));
            $exist_pdo[$cf['dsn']] = $pdo;
        }
        return $exist_pdo[$cf['dsn']];
    }

    /**
     * 开启事务
     */
    function begin_transaction() {
        $this->pdo->beginTransaction();
    }

    /**
     * 提交事务
     */
    function commit() {
        $this->pdo->commit();
    }

    /**
     * 回滚事务
     */
    function roll_back() {
        $this->pdo->rollBack();
    }

    /**
     * 设置当前操作的表
     * @param $table
     * @return $this
     */
    function table($table) {
        $this->table = $table;
        return $this;
    }

    /**
     * 插入数据到数据库
     * @param $data
     * @return int|false
     */
    function insert($data) {
        $inputs = array();
        $fields = array();
        $qs = array();
        foreach ($data as $key => $val) {
            $fields[] = $key;
            $qs[] = '?';
            $inputs[] = $val;
        }
        $sql = "insert into $this->table(" . join(',', $fields) . ') values(' . join(',', $qs) . ')';
        return $this->row_count($sql, $inputs);
    }


    /**
     * 更新数据库记录
     * @param $data
     * @param $where id或者where条件
     * @param bool|false $inputs sql参数值
     * @return int|false
     */
    function update($data, $where, $inputs = false) {
        $arr = array();
        $fields = array();
        foreach ($data as $key => $val) {
            $fields[] = " $key = ? ";
            $arr[] = $val;
        }
        $where = $this->parse_where($where);
        $sql = "update $this->table set " . join(',', $fields) . " where $where";
        return $this->row_count($sql, is_array($inputs) ? array_merge($arr, $inputs) : $arr);
    }

    private function row_count($sql, $inputs) {
        $result = $this->execute($sql, $inputs);
        return $result === false ? false : $result->rowCount();
    }


    private function parse_where($where) {
        return is_numeric($where) ? " id = $where " : $where;
    }

    /**
     * 删除数据库记录
     * @param $where id或者where条件
     * @param bool|false $inputs sql参数值
     * @return int|false
     */
    function delete($where, $inputs = false) {
        $where = $this->parse_where($where);
        $sql = "delete from $this->table where $where";
        return $this->row_count($sql, $inputs);
    }

    /**
     * 获取数据库一条记录
     * @param $where id或者where条件
     * @param bool|false $inputs sql参数值 如果该值为字符串，则视为 $field
     * @param string $field
     * @return val|false
     */
    function get($where, $inputs = false, $field = '*') {
        if (is_string($inputs)) {
            $field = $inputs;
        }
        $where = $this->parse_where($where);
        $sql = "select $field from  $this->table where $where";
        $result = $this->execute($sql, $inputs);
        return $result === false ? false : $result->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 获取第一行第一个值
     * @param $where id或者where条件
     * @param bool|false $inputs sql参数值
     * @param string $field
     * @return val|bool
     */
    function value($where, $inputs = false, $field = '*') {
        if (is_string($inputs)) {
            $field = $inputs;
        }
        $where = $this->parse_where($where);
        $sql = "select $field from  $this->table where $where";
        $result = $this->execute($sql, $inputs);
        if ($result === false) {
            return false;
        }
        $ret = $result->fetch(PDO::FETCH_NUM);
        return $ret === false ? false : $ret[0];
    }

    /**
     * 根据where条件获取记录集
     * @param $where where条件
     * @param bool|false $inputs sql参数值
     * @param string $field
     * @return array|false
     */
    function find($where, $inputs = false, $field = '*') {
        if (is_string($inputs)) {
            $field = $inputs;
        }
        $sql = "select $field from  $this->table where $where";
        return $this->select($sql, $inputs);
    }

    /**
     * 根据sql语句获取所有记录
     * @param $sql
     * @param bool|false $inputs
     * @return array|false
     */
    function select($sql, $inputs = false) {
        $result = $this->execute($sql, $inputs);
        return $result === false ? false : $result->fetchAll(PDO::FETCH_ASSOC);
    }

    private $result;

    /**
     * 执行一条sql语句
     * @param $sql
     * @param $inputs sql参数值
     * @return PDOStatement|false
     */
    function execute($sql, $inputs) {
        if (($this->result = $this->pdo->prepare($sql)) !== false) {
            $success = is_array($inputs) ? $this->result->execute($inputs) : $this->result->execute();
            if (!$success) {
                $this->error_info = $this->result->errorInfo();
                $this->print_error($sql, $inputs);
            }
        } else {
            $this->error_info = $this->pdo->errorInfo();
            $this->print_error($sql, $inputs);
        }
        return $this->result;
    }

    private function print_error($sql, $inputs) {
        echo '<pre>SQL ERROR: ';
        print_r(array(
            'sql' => $sql,
            'inputs' => $inputs,
            'error_info' => $this->error_info
        ));
        echo '</pre>';
    }


    /**
     * 数据库最后id
     * @return string
     */
    function last_id() {
        return $this->pdo->lastInsertId();
    }

    private $error_info;

    /**
     * 数据库最后语句的错误信息
     * @return mixed
     */
    function error_info() {
        return $this->error_info;
    }

    function __destruct() {
        if ($this->pdo) {
            unset($this->pdo);
        }
        if ($this->result) {
            unset($this->result);
        }
    }

}

/*** 简单数据库操作类 end***/
